/******************************************************************************
  A simple program of Hisilicon Hi3516A+OV4689 multi-media live implementation.

  Copyright (c) 2018 Liming Shao <lmshao@163.com>
******************************************************************************/
#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif /* End of #ifdef __cplusplus */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <arpa/inet.h>

#include "sample_comm.h"  
#include "Utils.h"
#include "RTP.h"
#include "Network.h"

  
/************ Global Variables ************/

typedef enum {
    MODE_FILE,
    MODE_RTP,
    MODE_RTSP
}RunMode;

typedef struct {
    RunMode mode;  // -m
    int frameRate;  // -f
    int bitRate;    // -b
    char ip[16];    // -i
    PAYLOAD_TYPE_E videoFormat;  // -e
    PIC_SIZE_E videoSize;   // -s
}ParamOption;

/************ Global Variables ************/
//VIDEO_NORM_E gs_enNorm = VIDEO_ENCODING_MODE_NTSC;
ParamOption gParamOption;
RTPMuxContext gRTPCtx;
UDPContext gUDPCtx;


/************ Show Usage ************/
void hiliShowUsage(char* sPrgNm)
{
    printf("Usage : %s \n", sPrgNm);
    printf("\t -m: mode: file/rtp, default file.\n");
    printf("\t -e: vedeo decode format, default H.264.\n");
    printf("\t -f: frame rate, default 24 fps.\n");
    printf("\t -b: bitrate, default 1024 kbps.\n");
    printf("\t -i: IP, default 192.168.1.100.\n");
    printf("\t -s: video size: 1080p/720p/D1/CIF, default 1080p\n");
    printf("Default parameters: %s -m file -e 96 -f 264 -b 1024 -s 1080p -i 192.168.1.100\n", sPrgNm);
    return;
}

/************ Parse Parameters ************/
int hiliParseParam(int argc, char**argv){
    int ret = 0, optIndex = 1;
    char *videoSize = "1080p";
    char *format = "H.264";
    char *mode = "file";

    if (argc % 2 == 0)
        return -1;

    // init default parameters
    gParamOption.mode = MODE_FILE;
    gParamOption.frameRate = 24;    // fps
    gParamOption.bitRate = 1024;    // kbps
    sprintf(gParamOption.ip, "%s", "192.168.16.61");
    gParamOption.videoSize = PIC_VGA;//PIC_HD1080;
    gParamOption.videoFormat = PT_H264;       // H.264

    // parse parameters
    while (optIndex < argc && !ret){
        const char *opt = argv[optIndex++];
        int val = 0;
        char *str = NULL;
        
        if (opt[0] == '-' && opt[1] == 'm' && !opt[2]){
            mode = argv[optIndex++];
            if (!strcmp(mode, "file") || !strcmp(mode, "FILE")){
                gParamOption.mode = MODE_FILE;
            } else if (!strcmp(mode, "rtp") || !strcmp(mode, "RTP")){
                gParamOption.mode = MODE_RTP;
            } else {
                printf("mode %s is invalid\n", mode);
                ret = -1;
            }
            continue;
        }

        if (opt[0] == '-' && opt[1] == 'e' && !opt[2]){
            format = argv[optIndex++];
            if (strstr(format, "264") || !strcmp(format, "AVC") || !strcmp(format, "avc")){
                gParamOption.videoFormat = PT_H264;
                LOGD("PT_H264\n");
            } else if (strstr(format, "265") || !strcmp(format, "HEVC") || !strcmp(format, "hevc")){
                gParamOption.videoFormat = PT_H265;
                LOGD("PT_H265\n");
            } else {
                printf("VedeoFormat is invalid.\n");
                ret = -1;
            }

            continue;
        }

        if (opt[0] == '-' && opt[1] == 'f' && !opt[2]){
            val = atoi(argv[optIndex++]);
            if (val <= 0 || val > 30){
                printf("frameRate is not in (0, 30]\n");
                ret = -1;
            } else
                gParamOption.frameRate = val;
            continue;
        }

        else if (opt[0] == '-' && opt[1] == 'b' && !opt[2]){
            val = atoi(argv[optIndex++]);
            if (val <= 0 || val > 4096){
                printf("bitRate is not in (0, 4096]\n");
                ret = -1;
            } else
                gParamOption.bitRate = val;
            continue;
        }

        else if (opt[0] == '-' && opt[1] == 'i' && !opt[2]){
            str = argv[optIndex++];
            if (inet_addr(str) == INADDR_NONE){
                printf("IP is invalid.\n");
                ret = -1;
            } else
                sprintf(gParamOption.ip, "%s", str);
            continue;
        }

        else if (opt[0] == '-' && opt[1] == 's' && !opt[2]){
            videoSize = argv[optIndex++];
                gParamOption.videoSize = PIC_VGA;//PIC_HD720;
            continue;
        }

        else {
            printf("param [%s] is invalid.\n", opt);
            ret = -1;
        }
    }

    printf("param:\nmode=%s, format=%s, frameRate=%d fps, bitRate=%d kbps, videoSize=%s, IP=%s\n",
           mode, format, gParamOption.frameRate,
           gParamOption.bitRate, videoSize, gParamOption.ip);

    return ret;
}
 

HI_S32 hiliRTPSendVideo(VENC_STREAM_S* pstStream)
{   //如果u32PackCount个包的时间一致，可以考虑把u32PackCount个数据拼成一个buff，一起发送。
    int i;
    gRTPCtx.payload_type = (gParamOption.videoFormat == PT_H264) ? 0 : 1;

    for (i = 0; i < pstStream->u32PackCount; i++) {
        //LOGD("packet %d/ %d, %lld", i, pstStream->u32PackCount, pstStream->pstPack[i].u64PTS);

        gRTPCtx.timestamp = (HI_U32) pstStream->pstPack[i].u64PTS * 9 / 100;    // (μs / 10^6) * (90 * 10^3) 

        rtpSendH264HEVC(&gRTPCtx, &gUDPCtx, 
                        pstStream->pstPack[i].pu8Addr + pstStream->pstPack[i].u32Offset,    // stream ptr
                        pstStream->pstPack[i].u32Len - pstStream->pstPack[i].u32Offset);    // stream length

    }
    
    return 0;
}

void SAMPLE_VENC_HandleSigEx(HI_S32 signo)
{
    if (SIGINT == signo || SIGTERM == signo)
    {
        SAMPLE_COMM_SYS_Exit();
        printf("\033[0;31mprogram termination abnormally!\033[0;39m\n");
    }
    exit(-1);
}

/******************************************************************************
* funciton : get stream from each channels and save them
******************************************************************************/
HI_VOID* hiliGetVencStreamProc(HI_VOID* p)
{
    // HI_S32 i=0;
    HI_S32 s32ChnTotal;
    SAMPLE_VENC_GETSTREAM_PARA_S* pstPara;
    HI_S32 maxfd = 0;
    struct timeval TimeoutVal;
    fd_set read_fds;
    HI_S32 VencFd;
    HI_CHAR aszFileName[FILE_NAME_LEN];
    FILE* pFile;

    VENC_CHN_STATUS_S stStat;//lyz modify from VENC_CHN_STAT_S
    VENC_STREAM_S stStream;
    HI_S32 s32Ret;
    VENC_CHN VencChn = 0;
    VENC_STREAM_BUF_INFO_S stStreamBufInfo[VENC_MAX_CHN_NUM];

    pstPara = (SAMPLE_VENC_GETSTREAM_PARA_S*)p;
    s32ChnTotal = pstPara->s32Cnt;

    /******************************************
     step 1:  check & prepare save-file & venc-fd
    ******************************************/
    /* decide the stream file name, and open file to save stream */
    if (gParamOption.mode == MODE_FILE) {
        if (gParamOption.videoFormat == PT_H264) { 
            LOGD("Payload = H.264/AVC\n");
            sprintf(aszFileName, "stream_%s.h264", "0");//getCurrentTime()
        } else if (gParamOption.videoFormat == PT_H265){
            LOGD("Payload = HEVC/H.265\n");
            sprintf(aszFileName, "stream_%s.h265", "1");//getCurrentTime()
        } else {
            LOGE("Video Format is invalid.\n");
            return NULL;
        }

        pFile = fopen(aszFileName, "wb");
        if (!pFile) {
            LOGE("open file[%s] failed!\n", aszFileName);
            return NULL;
        }
    }
    SAMPLE_PRT("hiliGetVencStreamProc s32ChnTotal: %d, aszFileName:%s!\n", s32ChnTotal, aszFileName);


    /* Set Venc Fd. */
    VencFd = HI_MPI_VENC_GetFd(VencChn);
    if (VencFd < 0) {
        LOGE("HI_MPI_VENC_GetFd failed with %#x!\n", VencFd);
        return NULL;
    }
    
    if (maxfd <= VencFd) {
        maxfd = VencFd;
    }

	s32Ret = HI_MPI_VENC_GetStreamBufInfo (VencChn, &stStreamBufInfo[0]);
	if (HI_SUCCESS != s32Ret)
	{
	    SAMPLE_PRT("HI_MPI_VENC_GetStreamBufInfo failed with %#x!\n", s32Ret);
	    return (void *)HI_FAILURE;
	}

    /******************************************
     step 2:  Start to get streams of each channel.
    ******************************************/
    while (HI_TRUE == pstPara->bThreadStart)
    {
        FD_ZERO(&read_fds);

        FD_SET(VencFd, &read_fds);

        TimeoutVal.tv_sec  = 2;
        TimeoutVal.tv_usec = 0;
        s32Ret = select(maxfd + 1, &read_fds, NULL, NULL, &TimeoutVal);
        if (s32Ret < 0) {
            LOGE("select failed!\n");
            break;
        } 
        else if (s32Ret == 0) {
            LOGE("get venc stream time out, exit thread\n");
            continue;
        }
        else {
            if (FD_ISSET(VencFd, &read_fds)) {
                /*******************************************************
                 step 2.1 : query how many packs in one-frame stream.
                *******************************************************/
                memset(&stStream, 0, sizeof(stStream));
                //s32Ret = HI_MPI_VENC_Query(VencChn, &stStat);
                s32Ret = HI_MPI_VENC_QueryStatus(VencChn, &stStat);
                if (HI_SUCCESS != s32Ret) {
                    LOGE("HI_MPI_VENC_Query failed with %#x!\n", s32Ret);
                    break;
                }
                    
                /*******************************************************
                 step 2.2 :suggest to check both u32CurPacks and u32LeftStreamFrames at the same time,for example:
                 if(0 == stStat.u32CurPacks || 0 == stStat.u32LeftStreamFrames)
                 {
                    SAMPLE_PRT("NOTE: Current  frame is NULL!\n");
                    continue;
                 }
                *******************************************************/
                if(0 == stStat.u32CurPacks) {
                    LOGE("NOTE: Current  frame is NULL!\n");
                    continue;
                }
                /*******************************************************
                 step 2.3 : malloc corresponding number of pack nodes.
                *******************************************************/
                stStream.pstPack = (VENC_PACK_S*)malloc(sizeof(VENC_PACK_S) * stStat.u32CurPacks);
                if (NULL == stStream.pstPack) {
                    LOGE("malloc stream pack failed!\n");
                    break;
                }

                /*******************************************************
                 step 2.4 : call mpi to get one-frame stream
                *******************************************************/
                stStream.u32PackCount = stStat.u32CurPacks;
                s32Ret = HI_MPI_VENC_GetStream(VencChn, &stStream, HI_TRUE);
                if (HI_SUCCESS != s32Ret) {
                    free(stStream.pstPack);
                    stStream.pstPack = NULL;
                    LOGE("HI_MPI_VENC_GetStream failed with %#x!\n", s32Ret);
                    break;
                }

                /*******************************************************
                 step 2.5 : save frame to file
                *******************************************************/
                if (gParamOption.mode == MODE_FILE) {
                    //s32Ret = SAMPLE_COMM_VENC_SaveStream(gParamOption.videoFormat, pFile, &stStream);
                    s32Ret = SAMPLE_COMM_VENC_SaveStream(pFile, &stStream);
                } 
                else if (gParamOption.mode == MODE_RTP) {
                    s32Ret = hiliRTPSendVideo(&stStream);
                }
                else {
                    LOGE("Current Mode is not supported.\n");
                    break;
                }

                if (HI_SUCCESS != s32Ret) {
                    free(stStream.pstPack);
                    stStream.pstPack = NULL;
                    LOGE("save stream failed!\n");
                    break;
                }
                /*******************************************************
                 step 2.6 : release stream
                *******************************************************/
                s32Ret = HI_MPI_VENC_ReleaseStream(0, &stStream);
                if (HI_SUCCESS != s32Ret) {
                    free(stStream.pstPack);
                    stStream.pstPack = NULL;
                    break;
                }
                
                /*******************************************************
                 step 2.7 : free pack nodes
                *******************************************************/
                free(stStream.pstPack);
                stStream.pstPack = NULL;
            }
        }
    }

    /*******************************************************
    * step 3 : close save-file
    *******************************************************/
    fclose(pFile);

    return NULL;
}
  
int test_start_get_stream(){

    SAMPLE_VENC_GETSTREAM_PARA_S stPara;
    pthread_t vencPid;
    /******************************************
     step 6: stream venc process -- get stream, then save it to file.
    ******************************************/
    stPara.bThreadStart = HI_TRUE;
    stPara.s32Cnt = 1; 

    int s32Ret = pthread_create(&vencPid, 0, hiliGetVencStreamProc, (HI_VOID*)&stPara);

    if (HI_SUCCESS != s32Ret)
    {
        LOGE("Start Venc failed!\n");
	return s32Ret;
    }

    GREEN("please press twice ENTER to exit this sample\n");
    getchar();
    getchar();

    /******************************************
     step 7: exit process
    ******************************************/
    // SAMPLE_COMM_VENC_StopGetStream();

    if (HI_TRUE == stPara.bThreadStart)
    {
        stPara.bThreadStart = HI_FALSE;
        pthread_join(vencPid, 0);
    }

    return s32Ret;
}

/******************************************************************************
* function    : main()
* Description : video venc sample
******************************************************************************/
int main(int argc, char* argv[])
{
    int res = 0;
    
    GREEN("+-------------------------+\n");
    GREEN("|         HisiLive        |\n");
    GREEN("|  %s %s   |\n", __DATE__, __TIME__);
    GREEN("+-------------------------+\n");

    res = hiliParseParam(argc, argv);
    if (res){
        hiliShowUsage(argv[0]);
        return -1;
    }

    signal(SIGINT, SAMPLE_VENC_HandleSigEx);
    signal(SIGTERM, SAMPLE_VENC_HandleSigEx);

    if (gParamOption.mode == MODE_RTP) {
        strcpy(gUDPCtx.dstIp, gParamOption.ip);
        gUDPCtx.dstPort = 1234;
        res = udpInit(&gUDPCtx);
        if (res){
            LOGE("udpInit error.\n");
            return -1;
        }

        initRTPMuxContext(&gRTPCtx);
        gRTPCtx.aggregation = 1;   // 1 use Aggregation Unit, 0 Single NALU Unit， default 0.
    }

    res = SAMPLE_VENC_H265_H264();
    if (res) { 
        RED("program exit abnormally!\n"); 
    } else {
        GREEN("program exit normally!\n");
    }

    return res;
}

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* End of #ifdef __cplusplus */
